﻿using System;
using System.Windows.Media;
using System.Reflection;
using System.Collections.Generic;
using System.Windows;
using DeepEarth.Map.Core.Utilities;

#if !WINDOWS_PHONE
using DeepEarth.Map.Core.Composition;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
#endif

namespace DeepEarth.Map.Core.Tiling
{
    public abstract class BaseTileSource : MultiScaleTileSource
    {
        // The Logical Bounds within which this overly has tiles
        // default to the whole world
        private Rect logicalBounds = new Rect(0, 0, 1, 1);
        public Rect LogicalBounds
        {
            get
            {
                return logicalBounds;
            }
            set
            {
                logicalBounds = value;
            }
        }

        // the maximum zoom level at which this overlay has data
        private double maxDataZoomLevel = double.MaxValue;
        public double MaxDataZoomLevel
        {
            get
            {
                return maxDataZoomLevel;
            }
            set
            {
                maxDataZoomLevel = value;
            }
        }

        private double minDataZoomLevel = 1;
        public double MinDataZoomLevel
        {
            get
            {
                return minDataZoomLevel;
            }
            set
            {
                minDataZoomLevel = value;
            }
        }

        public DateTime? Date { get; set; }

        public MapMode Mode { get; set; }

        protected BaseTileSource(MapMode mode, int imageWidth, int imageHeight, int tileWidth, int tileHeight, int tileOverlap)
            : this(imageWidth, imageHeight, tileWidth, tileHeight, tileOverlap)
        {
            this.Mode = mode;
            Initialize();
        }

        protected BaseTileSource(MapMode mode, long imageWidth, long imageHeight, int tileWidth, int tileHeight, int tileOverlap)
            : this(imageWidth, imageHeight, tileWidth, tileHeight, tileOverlap)
        {
            this.Mode = mode;
            Initialize();
        }

        protected BaseTileSource(int imageWidth, int imageHeight, int tileWidth, int tileHeight, int tileOverlap)
            : base(imageWidth, imageHeight, tileWidth, tileHeight, tileOverlap)
        {
            Initialize();
        }

        protected BaseTileSource(long imageWidth, long imageHeight, int tileWidth, int tileHeight, int tileOverlap)
            : base(imageWidth, imageHeight, tileWidth, tileHeight, tileOverlap)
        {
            Initialize();
        }

        List<TileInfo>[] ignoredTiles;

        void Initialize()
        {
            ignoredTiles = new List<TileInfo>[DefaultMaxZoomLevel];

            for (int i = 0; i < DefaultMaxZoomLevel; i++)
                ignoredTiles[i] = new List<TileInfo>();
        }

        bool Intersects(Rect a, Rect b)
        {
            a.Intersect(b);

            return !a.IsEmpty;
        }

        protected override void GetTileLayers(int tileLevel, int tilePositionX, int tilePositionY, IList<object> tileImageLayerSources)
        {
            int zoom = tileLevel - MapZoomLevelOffset;

            TileAction ta = ShouldRender(tileLevel, tilePositionX, tilePositionY);

            if (ta == TileAction.Render)
            {
                string path = GetTileUri(zoom, tilePositionX, tilePositionY);

                if (path != null)
                {
                    tileImageLayerSources.Add(new Uri(path));
                }
            }
            else if (ta == TileAction.Ignore)
            {
                List<TileInfo> tiles = ignoredTiles[zoom - 1];
                tiles.Add(new TileInfo { X = tilePositionX, Y = tilePositionY });
            }
        }

        protected virtual string GetTileUri(int zoom, int tilePositionX, int tilePositionY)
        {
            return string.Empty;
        }

        const int MapZoomLevelOffset = 8;
        const int DefaultMaxZoomLevel = 22;
        const int ZoomLevelCushion = 1;

        int minFetchThreshhold = 1;
        int MinFetchThreshhold
        {
            get { return minFetchThreshhold; }
            set
            {
                if (minFetchThreshhold != value)
                {
                    int old = minFetchThreshhold;
                    minFetchThreshhold = value;
                    if (this.minFetchThreshhold < old)
                        ClearIgnoredTiles();
                }
            }
        }

        public void OnZoomLevelChanged(double zoomLevel)
        {
            MinFetchThreshhold = (int)Math.Floor(zoomLevel - ZoomLevelCushion);
        }

        void ClearIgnoredTiles()
        {            
            for (int i = 0; i < ignoredTiles.Length; i++)
            {
                int tileLevel = i + 1 + MapZoomLevelOffset;

                List<TileInfo> tiles = ignoredTiles[i];
                foreach (var ti in tiles)
                {
                    this.InvalidateTileLayer(tileLevel, ti.X, ti.Y, 0);
                }

                tiles.Clear();
            }            
        }

        enum TileAction
        {
            Render, // Render the tile
            Ignore, // Ignore for this zoomlevel
            NoData  // No data exists at this zoomlevel
        }

        TileAction ShouldRender(int tileLevel, int tileX, int tileY)
        {
            int zoom = tileLevel - MapZoomLevelOffset;
            Rect tileLogicalBounds = TileSourceUtility.TileXYZoomToLogicalRect(tileX, tileY, zoom);

            // is tile in bounds
            if (Intersects(this.LogicalBounds, tileLogicalBounds))
            {
                // do we have data for this tile
                if (zoom >= MinDataZoomLevel && zoom <= MaxDataZoomLevel)
                {
                    // should the tile be ignored anyway as an optimisation
                    return zoom < MinFetchThreshhold ? TileAction.Ignore : TileAction.Render;                    
                }
            }
            return TileAction.NoData;
        }

        struct TileInfo
        {
            public int X { get; set; }
            public int Y { get; set; }
        }

    }
}
